﻿using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using myCompiler.RunTime;

namespace myCompiler {
    public class CompilerWorker {
        private static CompilerWorker instance;
        private readonly MainForm form;
        public AbstractSyntaxTree syntaxTree;

        private CompilerWorker(MainForm form2) {
            syntaxTree = new AbstractSyntaxTree();
            form = form2;
        }

        public static void InitializeInstance(MainForm form2) {
            instance = new CompilerWorker(form2);
        }

        public static void ClearInstance() {
            instance = null;
        }

        public static CompilerWorker GetInstance() {
            if (instance != null) return instance;
            else {
                throw new Exception("Compiler worker not initialized");
            }
        }

        public void AddMessage(string message) {
            form.Invoke(new AddMessageDelegate(form.AddMessage), message);
        }

        public void EmitCode() {
            Type mainClassType = null;
            Type workClassType = null;
            AppDomain currentDom = Thread.GetDomain();
            string asmFileName = "bob2.exe";
            AssemblyName myAsmName = new AssemblyName("MyDynamicAssembly");
            AssemblyBuilder myAsmBldr = currentDom.DefineDynamicAssembly(myAsmName,AssemblyBuilderAccess.Save);

            // We've created a dynamic assembly space - now, we need to create a module
            ModuleBuilder myModuleBldr = myAsmBldr.DefineDynamicModule(asmFileName);
            TypeBuilder workTypeBldr = myModuleBldr.DefineType("WorkClass");
            TypeBuilder mainTypeBldr = myModuleBldr.DefineType("MainClass");

            // Build the PointMain method.
            MethodBuilder pointMainBldr = mainTypeBldr.DefineMethod("PointMain", MethodAttributes.Public | MethodAttributes.Static, typeof(void), null);
            ILGenerator pmIL = pointMainBldr.GetILGenerator();

            // We have 2 methods that we wish to call, and must represent as
            //MethodInfo writeMI = typeof(Console).GetMethod("Write",new Type[] { typeof(string) });
            
            //Type[] wlParams = new Type[] { typeof(string), typeof(object[]) };
            //MethodInfo writeLineMI = typeof(Console).GetMethod("WriteLine",wlParams);

            //cstr info
            Type objType = Type.GetType("System.Object");
            ConstructorInfo objCtor = objType.GetConstructor(new Type[] { });

            Type[] ctorParams = new Type[] {  };
            ConstructorBuilder workCtor = workTypeBldr.DefineConstructor(
                                      MethodAttributes.Public,
                                      CallingConventions.Standard,
                                      ctorParams);
            ILGenerator ctorIL = workCtor.GetILGenerator();
            ctorIL.Emit(OpCodes.Ldarg_0);
            ctorIL.Emit(OpCodes.Call, objCtor);
            ctorIL.Emit(OpCodes.Ret);

            foreach (BasicAction action in this.syntaxTree.ActionList) {
                action.EmitCode(workTypeBldr);
            }

            workClassType = workTypeBldr.CreateType();

            pmIL.DeclareLocal(workClassType);
            //pmIL.Emit(OpCodes.Ldstr, "start gen code");
            //pmIL.EmitCall(OpCodes.Call, writeMI, null);

            pmIL.Emit(OpCodes.Newobj, workCtor);
            pmIL.Emit(OpCodes.Stloc_0);

            foreach (BasicAction action in this.syntaxTree.ActionList) {
                if (action.MethodName == "ScreenOutAction")
                {
                    pmIL.Emit(OpCodes.Ldstr, (action as ScreenOutAction).ValValue);
                    MethodInfo info = typeof(BasicFunction).GetMethod("ScreenOut");
                    pmIL.EmitCall(OpCodes.Call, info, new Type[] { typeof(object) });
                }
                else if(action.MethodName == "ScreenOutValAction")
                {
                    //need create a symbol table to resolve the val print
                    SymbolItem temp = this.syntaxTree.SymbolTable.FindLast(n =>
                    {
                        return n.SymbolName ==
                               (action as ScreenOutValAction).ValValue;
                    });
                    pmIL.Emit(OpCodes.Ldstr, temp.SymbolValue);
                    MethodInfo info = typeof(BasicFunction).GetMethod("ScreenOut");
                    pmIL.EmitCall(OpCodes.Call, info, new Type[] { typeof(object) });
                }
                else if (action.MethodName != null)
                {
                    pmIL.Emit(OpCodes.Ldloc_0);
                    pmIL.EmitCall(OpCodes.Callvirt, workTypeBldr.GetMethod(action.MethodName), new Type[] {});
                }
            }

            pmIL.Emit(OpCodes.Ret);
            mainClassType = mainTypeBldr.CreateType();

            myAsmBldr.SetEntryPoint(pointMainBldr);
            myAsmBldr.Save(asmFileName);
        }

        private delegate void AddMessageDelegate(string message);

    }
}